package server;

import java.net.*;  // for Socket, ServerSocket, and InetAddress
import java.util.ArrayList;
import java.io.*;   // for IOException and Input/OutputStream

import util.Logger;

import client.ClientDirectory;
import dataContracts.Command;

public class ServerProgram {
	// the list of connected clients
	ArrayList<ClientConnection> clients;
	
	final static String directoryPath = System.getProperty("user.home") + File.separator + "lanyard_server";
	ServerDirectory directory;
	int portNumber = 5001;

	public static void main(String[] args) throws IOException {
		ServerProgram program = new ServerProgram();
		if (program.environmentIsConfigured()) {
			program.run();
		} else {
			Logger.Error("You're environment is not configured. Create the following directories:\t"+directoryPath);
		}
	}
	
	private boolean environmentIsConfigured() {
		File server = new File(directoryPath);
		
		// if the directory doesn't exist, create it.
		if (!server.exists()) {
			server.mkdir();
		}
		
		return server.exists() && server.isDirectory();
	}

	private void run() {
		directory = new ServerDirectory(directoryPath);
		clients = new ArrayList<ClientConnection>();

		try {
			// Create a server socket to accept client connection requests
			ServerSocket servSock = new ServerSocket(portNumber);
			Logger.Info("Socket setup on port"+ portNumber);
			ObjectInputStream ois;

			while (true) { // Run forever, accepting and servicing connections
				Socket clntSock = servSock.accept();     // Get client connection
				ClientConnection client = new ClientConnection(clntSock);
				clients.add(client);
				
				// dispatcher: launch threads based on incoming command
				Thread t = new Thread(new ConnectionHandler(client, this));
				t.start();

				Logger.Info("Handling client at " +
						clntSock.getInetAddress().getHostAddress() + " on port " +
						clntSock.getPort());

			}
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}
	
	protected void notifyAllClients(String message) {
		for (ClientConnection client : clients) {
			client.notify(message);
		}
	}
	
	protected void commandAllClients(Command cmd) {
		for (ClientConnection client : clients) {
			client.command(cmd.commandType, cmd.commandBody);
		}
	}
	
	class ConnectionHandler implements Runnable {
		ClientConnection client;
		ServerProgram server;
		Command command;
		
		ConnectionHandler(ClientConnection client, ServerProgram server) {
			this.server = server;
			this.client = client;
		}
		
		@Override
		public void run() {
			// get the client message
			Command cmd;
			String incomingMessage;
			Thread t;
			
			try {
				while (true) {
					// blocking call to get the command object that was sent
					cmd = (Command)client.objectReader.readObject();
					incomingMessage = cmd.commandBody;
					
					switch (cmd.commandType) {
					case Broadcast:
						t = new Thread(new BroadcastHandler(client, server, incomingMessage));
						t.start();
						Logger.Debug("Broadcast - "+ incomingMessage);
						
						break;
					case IsEqual:
						t = new Thread(new EqualityHandler(client, server, incomingMessage));
						t.start();
						
						break;
					case GetFile:
						t = new Thread(new SendFileHandler(client, server, cmd, incomingMessage));
						t.start();
						
						break;
					case DeleteFile:
						t = new Thread(new DeleteFileHandler(client, server, incomingMessage));
						t.start();
						
						break;
					case Register:
						client.name = cmd.commandBody;
						notifyAllClients(client.name + " is now online");
						
						break;
					case RecvFile:
						SendFileHandler handler = new SendFileHandler(client, server, cmd, incomingMessage);
						handler.operation = 1;
						t = new Thread(handler);
						t.start();
						break;
					case PrepareForDirectoryObject:
						t = new Thread(new DirectoryHandler(client, server));
						t.start();
						
						break;
					case Synch:
						t = new Thread(new SynchHandler(client, server, cmd));
						t.start();
						
						break;
					default:
						notifyAllClients("Unknown command: "+ incomingMessage);
						break;
					}
					// clean up
					cmd = null;
				}
			} catch(EOFException eof) {
				client.closeConnection();
				clients.remove(client);
				notifyAllClients(client.name + " is offline");
				Logger.Debug("client disconnected: "+ client.name);
				Logger.Debug("clients remaining: "+ clients.size());
				
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
}